<?php 

/**
 * Contains the Log_XML_Xtj class.
 *
 * PHP version 5
 *
 * @category Logging
 * @package  Log
 * @author   Michael "Vladimyr" Gower <MichaelPeterGower@gmail.com>
 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
 * @link     http://pear.php.net/pepr/pepr-proposal-show.php?id=487
 */

/**
 * Require the base class.
 */
require_once 'Log/XML/DOM.php';


/**
 * Concrete subclass of the Log_XML class.  The Xtj stands for XML
 * (core technology), Transform (.xslt support), and Javascript (not used).
 * The schema was designed to be easily used with Javascript. The class
 * will save the log entries to a schema similar to the schema used in
 * "Using XML Technologies For Enhancing Log Files"
 * (http://www.gamedev.net/reference/programming/features/xmltech/).
 *
 * For $conf we support the following parameters:
 * Parameter - type              - default - Description
 * append    - boolean           - true    - Should we add to the old log or create a new one?
 * mode      - string or integer - 0644    - What permission's mode should we open the log in?
 * dirmode   - string or integer - 0755    - What permission mode should we open newly created directories in?
 * autoflush - boolean           - true    - Should we flush after every logging statement?  This is great for getting
 *     logs of crashes, but may incur a performance hit.
 * schema    - filename          - null    - What schema should we validate the log file against?
 * timezone  - string            - America/Los_Angeles - What timezone will we use for date functions?
 *     If null then we do not validate.
 *
 * @category Logging
 * @package  Log
 * @author   Michael "Vladimyr" Gower <MichaelPeterGower@gmail.com>
 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
 * @link     http://pear.php.net/pepr/pepr-proposal-show.php?id=487
 */
class Log_XML_DOM_Xtj extends Log_XML_DOM
    {

    public function __construct($filename = Log_XML::DEFAULT_FILENAME,
    $ident    = '',
    $conf     = array(),
    $level    = PEAR_LOG_DEBUG)
    {
        parent::__construct($filename, $ident, $conf, $level);


        if (isset($conf['schema'])) {
            $this->schema = $this->fileToolkit->getRealPath($conf['schema']);
        }

        $timezone = "America/Los_Angeles";
        if (isset($conf['timezone'])) {
            $timezone = $conf['timezone'];
        }
        date_default_timezone_set($timezone);
    }

    public function open()
    {
        $ret = parent::open();
        if (!$ret) {
            return false;
        }

        $this->xpath = new DOMXPath($this->domDocument);

            /* Determine whether the log file needs to be created. */
        $creating = !file_exists($this->filename);

        if (!$creating && $this->append === true) {
            // appending
            $this->int_baseId = $this->getBaseId();
                // adjust for daylight savings
            $this->startTimestamp = $this->getStartTimestamp()
                    + date('Z');
            $this->mask           = $this->getOutputLevel();
        } else {
            // creating
            $this->startTimestamp = time();
        }

        return true;
    }


    public function close()
    {
        if (parent::close()) {
            $this->startTimestamp = 0;
            $this->int_baseId     = 0;
            $this->xpath          = null;
            return true;
        } else {
            return false;
        }
    }

    /**
     * Add the header to the xml file.
     *
     * @return boolean true on success, false on failure
     */
    protected function createHeader()
    {
        // add root RunTimeLog node
        $node_root = $this->appendElement($this->domDocument,
        'RunTimeLog');

            // add RunTimeLog->LogHeader node
        $node_logHeader = $this->appendElement($node_root, 'LogHeader');

            // add RunTimeLog->LogHeader->OutputLevel node
        $this->appendElement($node_logHeader, 'OutputLevel', $this->mask);

            // add RunTimeLog->LogHeader->Session node
        $node_session = $this->appendElement($node_logHeader, 'Session');

            // add RunTimeLog->LogHeader->Session->Started node
        $node_started = $this->appendElement($node_session, 'Started');

            // add RunTimeLog->LogHeader->Session->Started->Time node
        $this->appendElement($node_started, 'Time', date('h:i:s A, T'));

            // add RunTimeLog->LogHeader->Session->Started->Date node
        $this->appendElement($node_started, 'Date', date('Y-F-d, l'));

            // add RunTimeLog->LogHeader->Session->Started->Timestamp node
        $this->appendElement($node_started, 'Timestamp', time());

            // flush all
        return true;
    }


    /**
     * Creates the log entry in the xml file.
     *
     * @param mixed  $message   the message to log
     * @param int    $type      the type of messsage being passed in
     * @param string $namespace the namespace the message came from
     *
     * @return boolean true on success, false on failure
     */
    protected function createLogEntry($message,
    $type = 1,
    $namespace = "default")
    {
        // get the file, line, and method
        list($string_file, $line, $method) = $this->_getBacktraceVars(2);

            // get the root node
        $node_root = $this->domDocument
                ->getElementsByTagName('RunTimeLog')->item(0);

            // add RunTimeLog->LogEvent node
        $node_logEvent = $this->appendElement($node_root,
        'LogEvent', null, true);

            // add RunTimeLog->LogEvent->Type node
        $this->appendElement($node_logEvent, 'Type', $type, false);

            // add RunTimeLog->LogEvent->TimeIndex
        $string_runningTime = $this->getRunningTime();
        $this->appendElement($node_logEvent, 'TimeIndex',
        $string_runningTime, false);

            // add RunTimeLog->LogEvent->NameSpace
        $this->appendElement($node_logEvent, 'NameSpace',
        $namespace, false);

            // add RunTimeLog->LogEvent->File
        $this->appendElement($node_logEvent, 'File', $string_file, false);

            // add RunTimeLog->LogEvent->Function
        $this->appendElement($node_logEvent, 'Function', $method, false);

            // add RunTimeLog->LogEvent->LineNumber
        $this->appendElement($node_logEvent, 'LineNumber', $line, false);

            // add RunTimeLog->LogEvent->Message
        $this->appendElement($node_logEvent, 'Message', $message, false);

        return true;
    }


    /**
     * Creates and appends a node.
     *
     * Can have text and/or an id field.
     *
     * @param DOMNode $node_parent the node to attach the new node to
     * @param string  $string_name the name of the new node,
     *      will come to &lt;$string_name&gt;
     * @param mixed   $value       the contents of the new node
     * @param bool    $bool_id     if the new node will have an id attribute or not
     *
     * @return DOMNode the new child node
     */
    protected function appendElement($node_parent, $string_name,
    $value = null, $bool_id = false)
    {
        $node_child = $this->domDocument->createElement($string_name);
        $node_parent->appendChild($node_child);
        if ($value !== null) {
            // add text node
            $node_childText = $this->domDocument->createTextNode($value);
            $node_child->appendChild($node_childText);
        }
        if ($bool_id === true) {
            // add id attribute
            // add id element
            $DOMAttr_id        = $this->domDocument->createAttribute('id');
            $DOMAttr_id->value = $this->getId();
            $node_child->appendChild($DOMAttr_id);
        }
        return $node_child;
    }


    /**
     * Returns the next id to use
     *
     * @return int the next id
     */
    protected function getId()
    {
        static $int_idOffset = 0;
        return $this->int_baseId + $int_idOffset++;
    }


    /**
     * Returns the highest id used.
     *
     * @return int the highest id used
     */
    protected function getBaseId()
    {
        if ($this->_opened !== true || !$this->xpath) {
            return 0;
        }
        $string_query = '//RunTimeLog/LogEvent';
        $DOMNodeList  = $this->xpath->query($string_query);
        return $DOMNodeList->length;
    }


    /**
     * Returns the current time since the log started.
     *
     * @return string $string_runningTime time since log started,
     *     in human readable format.
     */
    protected function getRunningTime()
    {
        // calculate seconds in a day
        $int_secondsInADay = 60 * 60 * 24;

            // find total seconds since log started
        $int_runningTime = time() - $this->startTimestamp;

            // find days running
        $int_days = floor($int_runningTime / $int_secondsInADay);

            // find seconds today
        $int_runningTimeToday = $int_runningTime % $int_secondsInADay;

            // make our string
        $string_runningTime = $int_days . "d "
                . date('H:i:s', $int_runningTimeToday);

        return $string_runningTime;
    }


    /**
     * Returns the StartTimestamp in the log header in the xml file
     *
     * @return int the StartTimestamp in the xml file
     */
    protected function getStartTimestamp()
    {
        if ($this->_opened !== true || !$this->xpath) {
            return 0;
        }
        $string_query = '//RunTimeLog/LogHeader/Session/Started/Timestamp';
        $DOMNodeList  = $this->xpath->query($string_query);
        if (null === $DOMNodeList || ! $DOMNodeList instanceof DOMNodeList) {
            throw new Exception("Something seriously wrong with DomNodeList[$DOMNodeList]");
        }
        $node = $DOMNodeList->item(0);
        if (null === $node || ! $node instanceof DOMNode) {
            throw new Exception("Something seriously wrong with node[$node]");
        }
        return $node->nodeValue;

    }


    /**
     * Gets the debug level of the log, as recorded in the xml file
     *
     * @return int returns the debug level of the log
     */
    protected function getOutputLevel()
    {
        if ($this->_opened !== true || !$this->xpath) {
            return 0;
        }
        $string_query = '//RunTimeLog/LogHeader/OutputLevel';
        $DOMNodeList  = $this->xpath->query($string_query);
        return $DOMNodeList->item(0)->nodeValue;
    }

    /**
     * The initial timestamp.
     * @var integer
     */
    protected $startTimestamp = 0;

    /**
     * The highest id used in the xml file when it was loaded.
     * @var integer
     */
    protected $int_baseId = 0;

    /**
     * A reference to the DOMXPath object for our DOMDocument.
     * @var DOMXPath
     */
    protected $xpath = null;

    /**
     * The unix file mask.
     */
    protected $mask = 0;
}
